#ifndef PORTABLE_SCOPES_H_
#define PORTABLE_SCOPES_H_

#include <vector>
#include <iomanip>

#include "src/pbc/portable-globals.h"
#include "src/pbc/portable-format.h"

namespace portable {

class Scope {
 public:
  explicit Scope(String name, uint32_t func_id)
      : outer_scope_(nullptr),
        inner_scope_(nullptr),
        sibling_(nullptr),
        function_name_(name),
        func_id_(func_id),
        locals_() {}
  Scope(String name, uint32_t func_id, Scope *outer)
      : outer_scope_(outer),
        inner_scope_(nullptr),
        sibling_(nullptr),
        function_name_(name),
        func_id_(func_id),
        locals_() {
    outer_scope_->AddInnerScope(this);
  }
  ~Scope() = default;

  String function_name() {
    return function_name_;
  }

  uint32_t func_id() const {
    return func_id_;
  }

  void AddInnerScope(Scope* inner_scope) {
    inner_scope->sibling_ = inner_scope_;
    inner_scope_ = inner_scope;
    inner_scope->outer_scope_ = this;
  }

  void DeclareLocal(const String &name) {
    locals_.emplace_back(name);
  }

  const std::vector<String> &GetLocals() const {
    return locals_;
  }

  const Scope *GetOuterScope() const {
    return outer_scope_;
  }

  const Scope *GetInnerScope() const {
    return inner_scope_;
  }

  const Scope *GetSiblingScope() const {
    return sibling_;
  }

  ScopeType GetScopeType() const {
    return scope_type_;
  }

  void SetScopeType(ScopeType type) {
    scope_type_ = type;
  }

  const char* getScopeTypeName() const {
    switch (scope_type_) {
      case kScriptScope: {
        return "ScriptScope";
      }
      case kFunctionScope: {
        return "FunctionScope";
      }
      case kBlockScope: {
        return "BlockScope";
      }
      case kCatchScope: {
        return "CatchScope";
      }
      case kWithScope: {
        return "WithScope";
      }
      case kEvalScope: {
        return "EvalScope";
      }
      case kClassScope: {
        return "ClassScope";
      }
      case kModuleScope: {
        return "ModuleScope";
      }
      case kReplModeScope: {
        return "kReplModeScope";
      }
      case kShadowRealmScope: {
        return "kShadowRealmScope";
      }
    }
  }

  void Print() const {
    // print header
    if (auto str = std::get_if<std::string>(&function_name_)) {
      std::cout << "Scope For Function: " << *str << std::endl;
    } else {
      auto u16str = std::get_if<std::u16string>(&function_name_);
      std::wcout << L"Scope For Function: ";
      for (char16_t c : *u16str) {
        std::wcout << static_cast<wchar_t>(c);
      }
      std::wcout << std::endl;
    }

    std::cout << "Locals:" << std::endl;

    // print contents
    int i = 0;
    for (const String &local : locals_) {
      if (auto str = std::get_if<std::string>(&local)) {
        std::cout << i << " : " << *str << std::endl;
      } else {
        auto u16str = std::get_if<std::u16string>(&local);
        std::wcout << i << L" : ";
        for (char16_t c : *u16str) {
          std::cout << "\\x" << std::setw(4) << std::setfill('0') << std::hex << static_cast<int32_t>(c);
        }
        std::wcout << std::endl;
      }
      i++;
    }
  }

 private:
  // Scope tree.
  Scope *outer_scope_;  // the immediately enclosing outer scope, or nullptr
  Scope *inner_scope_;  // an inner scope of this scope
  Scope *sibling_;  // a sibling inner scope of the outer scope of this scope.

  String function_name_;
  uint32_t func_id_;
  ScopeType scope_type_ = portable::kFunctionScope;
  std::vector<String> locals_;
};

}  // namespace portable

#endif  // PORTABLE_SCOPES_H_
